package com.google.code.boombox.receiver.raspberryPi;

import java.util.List;

import com.pi4j.io.gpio.GpioPinDigitalOutput;

/**
 * This class represents an encoder that takes, as input, a value and encodes
 * the output of several output pins accordingly in binary fashion. An encoder
 * created with N pins can encode values [0-2^N-1].
 * 
 * @author Mike
 * 
 */
public class Encoder
{
	/**
	 * The amount of time the decoder will pulse the enable pin high.
	 */
	public static final int ENABLE_PULSE_WIDTH_MILLIS = 1000;

	/**
	 * The maximum value that can be encoded with the provided output pins.
	 */
	private int maxEncodeVal;

	/**
	 * The digital outputs for setting encoded values
	 */
	private List<GpioPinDigitalOutput> outputPins;

	/**
	 * The digital output pin for enabling the encoder. This pin is used to
	 * indicate when all the output pins have been set.
	 */
	private GpioPinDigitalOutput enablePin;

	public Encoder(List<GpioPinDigitalOutput> outputPins,
			GpioPinDigitalOutput enablePin)
	{

		this.enablePin = enablePin;
		this.outputPins = outputPins;
		maxEncodeVal = (int) Math.pow(2.0, this.outputPins.size());

	}

	/**
	 * Sets the output pins to reflect the requested value and pulse enables the
	 * encoded output.
	 * 
	 * @param value
	 */
	public void pulse(int value)
	{
		if (value >= maxEncodeVal)
		{
			throw new IllegalArgumentException("Value to encode is too large: "
					+ value);
		}

		disableEncoder();

		setOutputs(value);

		pulseEnableEncoder();

		disableEncoder();
	}

	private void disableEncoder()
	{
		enablePin.low();

		setOutputs(0);
	}

	private void setOutputs(int value)
	{		
		for (int i = 0; i < outputPins.size(); i++)
		{
			if (isSet(value, i))
			{
				outputPins.get(i).high();
			}
		}
	}

	private void pulseEnableEncoder()
	{
		pulseEnableEncoder(ENABLE_PULSE_WIDTH_MILLIS);
	}

	private void pulseEnableEncoder(int periodMillis)
	{
		// wait for the pulse to be done
		enablePin.pulse(periodMillis, true);
	}

	/**
	 * Indicates if the indicated bit location is high in the provided value.
	 * 
	 * @param value
	 * @param bitLocation
	 * @return
	 */
	private boolean isSet(int value, int bitLocation)
	{
		return (((value >> bitLocation) & 1) == 1);
	}
}
